package com.xuecheng.content.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xuecheng.api.content.model.dto.CourseBaseDTO;
import com.xuecheng.api.content.model.dto.TeachplanDTO;
import com.xuecheng.api.content.model.dto.TeachplanMediaDTO;
import com.xuecheng.common.domain.code.CommonErrorCode;
import com.xuecheng.common.enums.common.CommonEnum;
import com.xuecheng.common.enums.content.CourseAuditEnum;
import com.xuecheng.common.enums.content.CourseModeEnum;
import com.xuecheng.common.enums.content.TeachPlanEnum;
import com.xuecheng.common.exception.ExceptionCast;
import com.xuecheng.common.util.StringUtil;
import com.xuecheng.content.agent.MediaApiAgent;
import com.xuecheng.content.common.constant.ContentErrorCode;
import com.xuecheng.content.convert.CourseBaseConvert;
import com.xuecheng.content.convert.TeachplanConvert;
import com.xuecheng.content.entity.CourseBase;
import com.xuecheng.content.entity.Teachplan;
import com.xuecheng.content.entity.TeachplanMedia;
import com.xuecheng.content.entity.ext.TeachplanNode;
import com.xuecheng.content.mapper.TeachplanMapper;
import com.xuecheng.content.service.CourseBaseService;
import com.xuecheng.content.service.TeachplanMediaService;
import com.xuecheng.content.service.TeachplanService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;

import java.util.ArrayList;
import java.util.List;

/**
 * <p>
 * 课程计划 服务实现类
 * </p>
 *
 * @author itcast
 */
@Slf4j
@Service
public class TeachplanServiceImpl extends ServiceImpl<TeachplanMapper, Teachplan> implements TeachplanService {

    @Autowired
    private CourseBaseService courseBaseService;

    @Autowired
    private TeachplanMediaService mediaService;

    @Autowired
    private MediaApiAgent mediaApiAgent;

    /**
     * 业务分析
     * 1.判断关键数据：操作时的必要数据（一般是由前端来传递）
     * courseId  companyId
     * 2.判断业务数据：操作时，判断数据的状态（一般是判断数据值，数据值来源于后端的数据源）
     * 课程基本信息
     * 判断是否是同一家教学机构
     * 判断课程是否删除
     * 3.获得课程下的所有课程计划数据
     * 4.通过java的递归来生成课程计划树形结构
     * 5.将node数据转为dto并返回
     *
     * @param courseId  - 课程id
     * @param companyId - 公司令牌
     * @return
     */
    @Override
    public TeachplanDTO queryTreeNodesByCourseId(Long courseId, Long companyId) {
        //1，判断关键数据：courseId，companyId存在
        //查询课程基础信息的这个方法刚好可以用，getByCourseId
        //不接受返回值就行了

        //2，判断业务数据：courseId，companyId符合业务状态。
        // 课程未删除，公司是同一家
        courseBaseService.getByCourseId(courseId, companyId);

        //3.获得课程下的所有课程计划数据
        TeachplanMapper baseMapper = this.getBaseMapper();
        List<TeachplanNode> nodes = baseMapper.selectTreeNodesByCourseId(courseId);

        TeachplanDTO dto = null;
        //4.通过java的递归来生成课程计划树形结构
        if (!(CollectionUtils.isEmpty(nodes))) {
            TeachplanNode root = nodes.remove(0);
            generateTreeNodes(root, nodes);
            //5.将node数据转为dto并返回
            dto = TeachplanConvert.INSTANCE.node2dto(root);
        } else {
            dto = new TeachplanDTO();
        }
        return dto;
    }


    /**
     * 业务分析：
     * 1，判断业务操作
     * 1.1是否添加：dto无id值
     * 添加操作（二级和三级课程计划），并返回TeachplanDTO
     * 1.2是否修改：dto有id值
     * 修改操作（二级和三级课程计划），并返回TeachplanDTO
     *
     * @param teachplanDTO - 教学计划DTO
     * @param companyId    - 公司令牌
     * @return - 教学计划dto
     */
    @Transactional
    @Override
    public TeachplanDTO createOrModifyTeachPlan(TeachplanDTO teachplanDTO, Long companyId) {
        //1,判断业务操作
        Long teachPlanId = teachplanDTO.getTeachPlanId();
        TeachplanDTO result = null;
        if (ObjectUtils.isEmpty(teachPlanId)) {
            //进来，无id。添加操作。
            result = createTeachplan(teachplanDTO, companyId);
        } else {
            //没进来，有id。修改操作.
            result = modifyTeachplan(teachplanDTO, companyId);
        }
        return null;
    }

    @Override
    public void removeTeachPlan(Long teachPlanId, Long companyId) {
        //1.判断关键数据的合法性
        Teachplan teachplan = getById(teachPlanId);

        if (ObjectUtils.isEmpty(teachplan)) ExceptionCast.cast(ContentErrorCode.E_120402);

        //1.2 判断课程基本信息是否存在
        CourseBase courseBase = courseBaseService.getById(teachplan.getCourseId());

        if (ObjectUtils.isEmpty(courseBase))
            ExceptionCast.cast(ContentErrorCode.E_120013);

        //1.3 判断公司 Id是否和课程中的公司Id一致
        if (!(ObjectUtils.nullSafeEquals(courseBase.getCompanyId(), companyId)))
            ExceptionCast.cast(CommonErrorCode.E_100108);


        // 2. 根据课程计划等级进行业务判断
        if (teachplan.getGrade() == TeachPlanEnum.SECEND_LEVEL) {

            // 判断二级课程计划是否有子级课程计划信息
            LambdaQueryWrapper<Teachplan> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(Teachplan::getParentid, teachPlanId);
            int count = count(queryWrapper);


            if (count > 0)
                ExceptionCast.cast(ContentErrorCode.E_120409);


        } else {

            // 判断三级课程计划是否关联课程媒资信息
            LambdaQueryWrapper<TeachplanMedia>
                    mediaLambdaQueryWrapper = new LambdaQueryWrapper<>();

            mediaLambdaQueryWrapper.eq(TeachplanMedia::getTeachplanId, teachPlanId);

            int mediaCount = mediaService.count(mediaLambdaQueryWrapper);

            if (mediaCount > 0) ExceptionCast.cast(ContentErrorCode.E_120413);

        }
        // 4.根据Id删除课程计划
        removeById(teachPlanId);
    }


    /**
     * 课程计划绑定媒资接口
     * 业务分析：
     * 1.判断关键数据：teachplanId mediaId companyId
     * 2.判断业务数据
     * 课程的计划
     * 判断是否是第三级
     * 判断课程类型是否录播（课程基本信息中获得并判断）
     * 判断课程计划是否存在：teachplanId、courseId
     * <p>
     * 课程基本信息
     * 判断是否存在或是否是同一家机构
     * 判断是否删除
     * 判断审核状态：未提交、审核未通过
     * <p>
     * 媒资信息
     * 判断媒资信息是否存在
     * 判断是否是同一家机构
     * 判断媒资审核状态
     * <p>
     * 3.将dto数据转为po数据并保存到teachplanmedia表中
     * 添加或修改
     * 如果存在：对课程计划的媒资信息进行修改：mediaId，mediaFileName
     * 如果不存在：课程计划和媒资信息的添加
     *
     * @param dto       - 媒资绑定课程计划dto
     * @param companyId - 公司令牌
     * @return - 媒资绑定课程计划dto
     */
    @Override
    @Transactional
    public TeachplanMediaDTO associateMedia(TeachplanMediaDTO dto, Long companyId) {
        //1.判断关键数据：teachplanId mediaId companyId
        Long teachplanId = dto.getTeachplanId();
        Long mediaId = dto.getMediaId();
        if (ObjectUtils.isEmpty(teachplanId) ||
                ObjectUtils.isEmpty(mediaId) ||
                ObjectUtils.isEmpty(companyId)) {
            ExceptionCast.cast(CommonErrorCode.E_100101);
        }
        //2.判断业务数据
        //        课程的计划
        //2.1判断是否是第三级
        //判断课程计划是否存在：teachplanId、courseId
        Teachplan teachplan = this.getById(teachplanId);
        if (ObjectUtils.isEmpty(teachplan)) {
            ExceptionCast.cast(ContentErrorCode.E_120402);
        }
        Integer grade = teachplan.getGrade();
        if (!(TeachPlanEnum.THIRD_LEVEL.equals(grade))) {
            ExceptionCast.cast(ContentErrorCode.E_120410);
        }
        Long courseId = teachplan.getCourseId();

        //课程基本信息
        //判断是否存在或是否是同一家机构
        CourseBaseDTO courseBaseDTO = courseBaseService.getByCourseId(courseId, companyId);
        //判断是否删除
        Integer status = courseBaseDTO.getStatus();
        if (CommonEnum.DELETE_FLAG.getCodeInt().equals(status)){
            ExceptionCast.cast(ContentErrorCode.E_120020);
        }
        //判断审核状态：未提交、审核未通过
        String auditStatus = courseBaseDTO.getAuditStatus();
        if (!(CourseAuditEnum.AUDIT_UNPAST_STATUS.getCode().equals(auditStatus)||
                CourseAuditEnum.AUDIT_DISPAST_STATUS.getCode().equals(auditStatus))){
            ExceptionCast.cast(ContentErrorCode.E_120015);
        }

        //判断课程类型是否录播（课程基本信息中获得并判断）
        String teachmode = courseBaseDTO.getTeachmode();
        if (CourseModeEnum.COURSE_MODE_LIVE_STATUS.getCode().equals(teachmode)){

        }
        return null;
    }


    /**
     * 添加操作
     * <p>
     * 业务分析：
     * 1，判断关键数据
     * pname：课程计划名称(因为只有这一个)
     * 2，判断业务数据
     * 课程基本信息
     * 判断课程基本信息是否是同一家机构
     * 判断课程是否删除
     * 判断课程审核状态：只有审核未通过或未提交才可以操作
     * 3，将dto数据转为po数据
     * 4，获得父级节点数据（新添加的数据是没有parentid）
     * 获得parentid
     * 获得父级节点的等级：给新添加的数据赋值grade
     * 获得父级节点的子节点的个数：给新添加的数据赋值orderby
     * 让课程计划和课程关联
     * 5，添加课程计划数据
     * 6，转为dto并返回
     *
     * @param dto       - 课程计划dto
     * @param companyId - 公司Id
     * @return
     */
    private TeachplanDTO createTeachplan(TeachplanDTO dto, Long companyId) {

        //1，判断关键数据
        if (StringUtil.isBlank(dto.getPname())) {
            ExceptionCast.cast(ContentErrorCode.E_120004);
        }

        //2，判断业务数据:课程基本信息
        CourseBase courseBase = verifyCourseInfo(dto, companyId);

        //3，将dto数据转为po数据
        Teachplan teachplan = TeachplanConvert.INSTANCE.dto2entity(dto);

        //4，获得父级节点数据（新添加的数据是没有parentid）
        Teachplan parentNode = getParentNode(courseBase, teachplan);

        //5，将当前的课程计划和父级数据进行关联
        teachplan.setParentid(parentNode.getId());

        //6,设置当前课程计划的级别
        teachplan.setGrade(parentNode.getGrade() + 1);

        //7,设置当前课程计划的排序orderBy
        //当前的teachplan的order：父级拥有的子级数据的个数+1
        LambdaQueryWrapper<Teachplan> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Teachplan::getParentid, parentNode.getId());

        int count = this.count(queryWrapper);

        teachplan.setOrderby(count + 1);

        //保存课程基本信息数据
        boolean result = this.save(teachplan);
        if (!result) {
            ExceptionCast.cast(ContentErrorCode.E_120407);
        }

        //将数据转为dto数据并返回
        TeachplanDTO teachplanDTO = TeachplanConvert.INSTANCE.entity2dto(teachplan);

        return teachplanDTO;
    }


    /**
     * 修改操作--课程计划
     * 1.判断关键数据
     * 2.判断是否有课程数据：courseId
     * 3.判断课程数据是否存在
     * 4.判断课程审核状态
     * 5.判断课程是否删除
     * 6.判断是否是同一个机构
     * 7.修改课程计划数据
     * 有些数据是不让前端来修改:parentId\grade\orderBy\courseId
     * 8.判断修改的结果:依据数据：课程计划的id值 where id =xxx
     * 9.返回结果数据 po-> dto
     *
     * @param dto       - 课程计划dto
     * @param companyId - 公司令牌
     * @return - 课程计划dto
     */
    private TeachplanDTO modifyTeachplan(TeachplanDTO dto, Long companyId) {
        //1，判断关键数据
        if (StringUtil.isBlank(dto.getPname())) {
            ExceptionCast.cast(ContentErrorCode.E_120004);
        }

        //2，判断业务数据:课程基本信息
        //courseId\companyId\课程是否删除\同一机构\审核状态
        verifyCourseInfo(dto, companyId);

        //修改课程计划数据
        //判断课程计划是否存在
        Teachplan teachplan = this.getById(dto.getTeachPlanId());
        if (ObjectUtils.isEmpty(teachplan)) {
            ExceptionCast.cast(ContentErrorCode.E_120402);
        }

        // 有些数据是不让前端来修改：parentid、grade、orderby、courseid
        // 信息修改的内容：pname 、 is_preview、media_type、starttime、endtime

        LambdaUpdateWrapper<Teachplan> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.set(Teachplan::getPname, dto.getPname());
        updateWrapper.set(Teachplan::getIsPreview, dto.getIsPreview());
        updateWrapper.set(Teachplan::getMediaType, dto.getMediaType());
        updateWrapper.set(Teachplan::getStartTime, dto.getStartTime());
        updateWrapper.set(Teachplan::getEndTime, dto.getEndTime());
        updateWrapper.eq(Teachplan::getId, dto.getTeachPlanId());

        boolean result = this.update(updateWrapper);

        if (!result) {
            ExceptionCast.cast(ContentErrorCode.E_120407);
        }

        //再去拿新数据
        Teachplan newTeachplan = this.getById(dto.getTeachPlanId());

        // 返回结果数据 po-> dto
        TeachplanDTO newDto = TeachplanConvert.INSTANCE.entity2dto(newTeachplan);
        return newDto;
    }


    /**
     * 获得课程计划的父级节点
     *
     * @param courseBase - 课程信息
     * @param teachplan  - 课程计划
     * @return 父级节点
     */
    private Teachplan getParentNode(CourseBase courseBase, Teachplan teachplan) {
        //获得parentid
        //获得父级节点的等级：给新添加的数据赋值grade
        //获得父级节点的子节点的个数：给新添加的数据赋值orderby
        if (ObjectUtils.isEmpty(teachplan.getParentid())) {
            //parentId为空，说明是二级

            //获得一级课程计划
            LambdaQueryWrapper<Teachplan> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(Teachplan::getParentid, TeachPlanEnum.FIRST_PARENTID_FLAG);
            queryWrapper.eq(Teachplan::getCourseId, teachplan.getCourseId());
            Teachplan rootNode = this.getOne(queryWrapper);
            if (ObjectUtils.isEmpty(rootNode)) {
                //父级不存在（一级）：自动创建并获得父级Id值
                rootNode = new Teachplan();
                rootNode.setPname(courseBase.getName());
                rootNode.setParentid(new Long(TeachPlanEnum.FIRST_PARENTID_FLAG));
                rootNode.setGrade(TeachPlanEnum.FIRST_LEVEL);
                rootNode.setDescription(courseBase.getDescription());
                rootNode.setOrderby(TeachPlanEnum.FIRST_LEVEL);
                rootNode.setCourseId(courseBase.getId());

                boolean result = this.save(rootNode);
                if (!result) {
                    ExceptionCast.cast(ContentErrorCode.E_120415);
                }
            }
            //父级存在，获得父级id
            return rootNode;
        } else {
            //parentId不为空，说明是三级
            //获得二级课程计划数据
            Teachplan secNode = this.getById(teachplan.getParentid());
            if (ObjectUtils.isEmpty(secNode)) {
                ExceptionCast.cast(ContentErrorCode.E_120408);
            }
            return secNode;
        }
    }


    /**
     * 判断课程基本信息是否是同一家机构
     * 判断课程是否删除
     * 判断课程审核状态：只有审核未通过或未提交才可以操作
     *
     * @param dto       - 课程计划dto
     * @param companyId -公司令牌
     */
    private CourseBase verifyCourseInfo(TeachplanDTO dto, Long companyId) {
        //2.1判断课程基本信息是否是同一家机构
        //2.2判断课程是否删除
        CourseBaseDTO courseBaseDTO = courseBaseService.getByCourseId(dto.getCourseId(), companyId);

        //2.3判断课程审核状态：只有审核未通过或未提交才可以操作
        String auditStatus = courseBaseDTO.getAuditStatus();
        if (CourseAuditEnum.AUDIT_COMMIT_STATUS.getCode().equals(auditStatus) ||
                CourseAuditEnum.AUDIT_PUBLISHED_STATUS.getCode().equals(auditStatus) ||
                CourseAuditEnum.AUDIT_PASTED_STATUS.getCode().equals(auditStatus) ||
                CourseAuditEnum.AUDIT_UNLINE_STATUS.getCode().equals(auditStatus)) {
            ExceptionCast.cast(ContentErrorCode.E_120015);
        }
        CourseBase courseBase = CourseBaseConvert.INSTANCE.dto2entity(courseBaseDTO);
        return courseBase;
    }

    /**
     * 递归生成树形结构数据
     *
     * @param currentNode - 当前的父级
     * @param nodes       - 集合数据
     */
    public void generateTreeNodes(TeachplanNode currentNode, List<TeachplanNode> nodes) {

        //判断父级的集合是否为空，为空则需要创建出集合对象
        if (ObjectUtils.isEmpty(currentNode.getChildrenNodes())) {
            currentNode.setChildrenNodes(new ArrayList<TeachplanNode>());
        }

        //循环 nodes 集合数据
        for (TeachplanNode node : nodes) {
            //1，判断循环的当前数据的parentId是否等于当前的currentNode的id
            if (node.getParentid().equals(currentNode.getId())) {
                //1.1将 循环中的node添加到当前父级node集合数据中
                currentNode.getChildrenNodes().add(node);
                //1.2判断循环中的node的级别是否为3级，如果不为3级，需要进行下一轮递归操作
                if (!(node.getGrade().equals(TeachPlanEnum.THIRD_LEVEL))) {
                    generateTreeNodes(node, nodes);
                }
            }
        }


    }
}
